<template>
  <div class="ms-game">
    <div class="header">
      <h2>扫雷 Minesweeper</h2>
      <div class="stats">
        <div>雷数：{{ minesTotal }}</div>
        <div>剩余标记：{{ flagsLeft }}</div>
      </div>
      <div class="actions">
        <button class="btn" @click="newGame">新开一局</button>
        <button class="btn" @click="toggleHelp">玩法说明</button>
      </div>
    </div>

    <div class="board">
      <div v-for="(row, y) in board" :key="'row-'+y" class="row">
        <div v-for="(cell, x) in row" :key="'cell-'+y+'-'+x" class="cell"
             :class="cellClass(cell)"
             @click="onReveal(y, x)"
             @contextmenu.prevent="onFlag(y, x)">
          <span v-if="cell.revealed && cell.mine">💣</span>
          <span v-else-if="cell.revealed && cell.number > 0" :class="'n'+cell.number">{{ cell.number }}</span>
          <span v-else-if="cell.flag">🚩</span>
        </div>
      </div>
      <div v-if="gameOver && !gameWon" class="overlay over">
        <div class="msg">踩到地雷了！</div>
        <button class="btn" @click="newGame">再来一局</button>
      </div>
      <div v-if="gameWon" class="overlay win">
        <div class="msg">恭喜通关！</div>
        <button class="btn" @click="newGame">再来一局</button>
      </div>
    </div>

    <div v-if="showHelp" class="help">
      - 左键点击揭开格子，遇到数字表示周围雷的数量。
      - 右键点击标记可能有雷的位置（🚩）。
      - 揭开所有非雷格子即可胜利。
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const width = ref(10)
const height = ref(10)
const minesTotal = ref(15)

function makeEmptyBoard(w, h) {
  const b = []
  for (let y = 0; y < h; y++) {
    const row = []
    for (let x = 0; x < w; x++) row.push({ revealed: false, mine: false, flag: false, number: 0 })
    b.push(row)
  }
  return b
}

function placeMines(b, w, h, count) {
  let placed = 0
  while (placed < count) {
    const y = Math.floor(Math.random() * h)
    const x = Math.floor(Math.random() * w)
    if (!b[y][x].mine) { b[y][x].mine = true; placed++ }
  }
}

const dirs = [
  [-1,-1],[-1,0],[-1,1],
  [0,-1],[0,1],
  [1,-1],[1,0],[1,1]
]

function computeNumbers(b, w, h) {
  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      if (b[y][x].mine) continue
      let n = 0
      for (const [dy, dx] of dirs) {
        const ny = y + dy, nx = x + dx
        if (ny >= 0 && ny < h && nx >= 0 && nx < w && b[ny][nx].mine) n++
      }
      b[y][x].number = n
    }
  }
}

const board = ref([])
const gameOver = ref(false)
const gameWon = ref(false)
const showHelp = ref(false)

function buildBoard() {
  const b = makeEmptyBoard(width.value, height.value)
  placeMines(b, width.value, height.value, minesTotal.value)
  computeNumbers(b, width.value, height.value)
  board.value = b
}

function newGame() {
  gameOver.value = false
  gameWon.value = false
  buildBoard()
}

function toggleHelp() { showHelp.value = !showHelp.value }

function floodReveal(y, x) {
  const w = width.value, h = height.value
  const q = [[y, x]]
  while (q.length) {
    const [cy, cx] = q.shift()
    const cell = board.value[cy][cx]
    if (cell.revealed || cell.flag) continue
    cell.revealed = true
    if (cell.number === 0) {
      for (const [dy, dx] of dirs) {
        const ny = cy + dy, nx = cx + dx
        if (ny >= 0 && ny < h && nx >= 0 && nx < w) {
          const ncell = board.value[ny][nx]
          if (!ncell.revealed && !ncell.mine) q.push([ny, nx])
        }
      }
    }
  }
}

function checkWin() {
  const w = width.value, h = height.value
  let revealed = 0
  let totalSafe = w * h - minesTotal.value
  for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (board.value[y][x].revealed && !board.value[y][x].mine) revealed++
  if (revealed === totalSafe) gameWon.value = true
}

function onReveal(y, x) {
  if (gameOver.value || gameWon.value) return
  const cell = board.value[y][x]
  if (cell.flag || cell.revealed) return
  if (cell.mine) {
    cell.revealed = true
    gameOver.value = true
    // reveal all mines
    for (let ry = 0; ry < height.value; ry++) for (let rx = 0; rx < width.value; rx++) if (board.value[ry][rx].mine) board.value[ry][rx].revealed = true
    return
  }
  if (cell.number === 0) floodReveal(y, x)
  else cell.revealed = true
  checkWin()
}

function onFlag(y, x) {
  if (gameOver.value || gameWon.value) return
  const cell = board.value[y][x]
  if (cell.revealed) return
  cell.flag = !cell.flag
}

const flagsLeft = computed(() => {
  let used = 0
  for (let y = 0; y < height.value; y++) for (let x = 0; x < width.value; x++) if (board.value[y][x].flag) used++
  return Math.max(minesTotal.value - used, 0)
})

newGame()

function cellClass(cell) {
  const cls = ['cell']
  if (cell.revealed) cls.push('revealed')
  if (cell.flag) cls.push('flagged')
  if (cell.mine && gameOver.value) cls.push('mine')
  return cls.join(' ')
}
</script>

<style scoped>
.ms-game { max-width: 420px; margin: 16px auto; font-family: system-ui, -apple-system, Segoe UI, PingFang SC, Microsoft YaHei; }
.header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.stats { display:flex; gap:12px; }
.actions { display:flex; gap:8px; }
.btn { padding:6px 10px; border:none; border-radius:6px; background:#4a699c; color:#fff; cursor:pointer; }
.btn:hover { filter:brightness(1.05); }
.board { margin-top:12px; background:#9aa1a8; padding:10px; border-radius:8px; display:grid; grid-template-rows: repeat(10, 1fr); gap:6px; position:relative; }
.row { display:grid; grid-template-columns: repeat(10, 1fr); gap:6px; }
.cell { background:#cfd6dd; border-radius:6px; height:34px; display:flex; align-items:center; justify-content:center; font-weight:700; color:#2d3e50; user-select:none; }
.cell:hover { filter:brightness(1.02); }
.cell.revealed { background:#eaeef2; }
.cell.flagged { background:#f9e3e3; }
.cell.mine { background:#f8d7da; color:#a94442; }
.overlay { position:absolute; inset:10px; border-radius:8px; background:rgba(255,255,255,.85); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; text-align:center; }
.help { margin-top:10px; background:#f8f6f2; border:1px solid #eee; border-radius:8px; padding:8px 10px; color:#3a4a5a; font-size:14px; }
.n1 { color:#1e90ff; } .n2 { color:#28a745; } .n3 { color:#dc3545; } .n4 { color:#6f42c1; } .n5 { color:#fd7e14; } .n6 { color:#20c997; } .n7 { color:#343a40; } .n8 { color:#6c757d; }
</style>